/*
 * exame_2006_07_1a_mailbox_splitter.c
 *
 *  Created on: Jun 8, 2012
 *      Author: ubuntu
 */

/*
 * Para testar:
 * ubuntu@ubuntu-VirtualBox:~/workspace/so/src/exames$ make mboxSplitter
 * ubuntu@ubuntu-VirtualBox:~/workspace/so/src/exames$ ./mboxsplitter.exe
 bota spam
 virus perigoso
 email giro
 outro email nice
 olha um virus
 spam sobre censa
 ultimo email importante
 Control + D para passar o EOF
 Mail carregado: 112 caracteres.
 Filtro spam aplicado
 Filtro virus aplicado
 mail.mbox updated.
 ubuntu@ubuntu-VirtualBox:~/workspace/so/src/exames$ cat spam.mbox
 bota spam
 spam sobre censa
 ubuntu@ubuntu-VirtualBox:~/workspace/so/src/exames$ cat virus.mbox
 virus perigoso
 olha um virus
 ubuntu@ubuntu-VirtualBox:~/workspace/so/src/exames$ cat mail.mbox
 email giro
 outro email nice
 ultimo email importante
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

// Relembrar da definicao da funcao pipe
// pipefd[0] refers
// to the read end of the pipe.  pipefd[1] refers to the write end of the pipe.
// Assim, vamos guardar estes valores, 0 em READ, e 1 em WRITE :
#define READ 0
#define WRITE 1
#define BUFSIZE 16384

void fillbuffer(int fp, char *buf, int size);
void sendbuffer(int fp, char *buf, int size);
void testmail(char *progname, char *filename, char *mail);
void fail(char *msg);

int main(void) {
	// file descriptor,
	// a small, nonnegative integer  for  use  in  subsequent  system  calls
	int fp;
	char buf[BUFSIZE];

	// Standard file descriptors.
	// #define	STDIN_FILENO	0	Standard input.
	// #define	STDOUT_FILENO	1	Standard output.
	// #define	STDERR_FILENO	2	Standard error output.

	// Assim, ao passarmos STDIN_FILENO estamos a pedir para ler do input
	fillbuffer(STDIN_FILENO, buf, BUFSIZE);
	printf("Mail carregado: %d caracteres.\n", strlen(buf));

	testmail("spamid.exe", "spam.mbox", buf);
	printf("Filtro spam aplicado\n");

	testmail("virusid.exe", "virus.mbox", buf);
	printf("Filtro virus aplicado\n");

	/* All checks passed */

	fp = open("mail.mbox", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR // associar permissoes de leitura e escrita ao novo ficheiro criado
	);
	sendbuffer(fp, buf, strlen(buf));
	close(fp);
	printf("mail.mbox updated.\n");

	exit(EXIT_SUCCESS);
}

void fillbuffer(int fp, char *buf, int size) {

	int nbytes, total = 0;
	do {
		// ssize_t read(int fd, void *buf, size_t count);

		// read()  attempts to read up to count bytes from file descriptor fd into
		// the buffer starting at buf.

		nbytes = read(fp, buf + total, size - total);
		// buf + total pq?
		// Ora, como estamos dentro de um ciclo, e não queremos ler sempre para o mesmo sítio,
		// dizemos para escrever a partir do ponto que já escrevemos,
		// ou seja a partir da variavel 'total' que está a registar os bytes lidos.

		total += nbytes;

	} while (nbytes > 0 && total < size - 1);
	if (nbytes == -1) { // RETURN VALUE
		// On success, the number of bytes read is returned (zero indicates end of
		// file). On error, -1 is returned, and errno is set
		// appropriately.
		fail("read");
	}
	size--; // ?

	// colocar o file terminator
	buf[(total < size ? total : size)] = '\0';

	return;
}

void sendbuffer(int fp, char *buf, int size) {
	int nbytes, total = 0;
	do {
		// ssize_t write(int fd, const void *buf, size_t count);
		// write()  writes  up  to  count bytes from the buffer pointed buf to the
		// file referred to by the file descriptor fd.

		nbytes = write(fp, buf + total, size - total);
		total += nbytes;
	} while (nbytes > 0 && total < size);
	// pq nbytes > 0
	// Pq enquanto for > 0 quer dizer q escreveu
	// Qd for = 0, quer dizer que já não escreveu nada
	// Qd for = -1, quer dizer que deu erro!
	if (nbytes == -1) // -1, tal como no caso do read (e maioria das funções já agora) é sinal de erro
		fail("write");
	return;
}

void testmail(char *progname, char *filename, char *mail) {
	// arrays a utilizar com os pipes
	int fdreceive[2], fdsend[2];

	// variavel a utlizar com o fork
	pid_t pid;

	// pipe()
	// creates  a pipe, a unidirectional data channel that can be used
	// for interprocess communication.  The array pipefd is used to return two
	// file  descriptors  referring to the ends of the pipe.  pipefd[0] refers
	// to the read end of the pipe.  pipefd[1] refers to the write end of  the
	// pipe.   Data  written  to  the write end of the pipe is buffered by the
	// kernel until it is read from the read end of  the  pipe.

	pipe(fdreceive);
	pipe(fdsend);

	// Repare-se que a funcao pipe, foi invocada antes da funcao fork!
	// The  above command creates  a pipe, and then fork(2)s creates
	// child process; the child inherits a duplicate set of  file  descriptors
	// that  refer  to  the same pipe.

	pid = fork();

	// After the fork(2), each process closes
	// the descriptors that it doesn't need for the pipe
	// E repare-se que é exactamente o que vai acontecer em baixo:
	// Por exemplo,
	// 1: o filho close(fdreceive[READ])
	// 2: o pai close(fdreceive[WRITE]);

	// Isto foi feito porque?
	// Porque o filho está interessado em escrever em fdreceive, logo o filho
	// vai fechar o file descriptor de escrita.
	// Por sua vez, o pai está interessado em ler/receber de fdreceive, logo pai
	// vai fechar o file descriptor de leitura, pq está interessado em ler!.

	// Relembre-se a definição:
	// pipe()
	// creates  a pipe, a unidirectional data channel that can be used
	// for interprocess communication.
	// Destaco a parte "unidirectional"!
	// Logo, se queremos ler e escrever, em ambos os sentidos, precisamos de criar
	// dois pipes, que é exactamente o que estamos a fazer aqui:
	// criamos um pipe de onde o pai vai ler: pipe(fdreceive);
	// e um outro pipe, onde o pai vai escrever: pipe(fdsend);

	// Depois desta breve reflexao do que estamos a fazer, bora então, seguir caminho.

	switch (pid) {

	case -1: // failure
		fail("fork");
		break;

	case 0: // child
		// Como vimos atrás, vamos fechar "as pontas" que não nos interessam,
		// em cada um dos pipe/ em cada um dos canais de comunicação
		close(fdreceive[READ]);
		close(fdsend[WRITE]);

		// dup2
		// int dup2(int oldfd, int newfd);

		// If oldfd is a valid file descriptor, and newfd has the same value as
		// oldfd, then dup2() does nothing, and returns newfd.
		if (fdsend[READ] != STDIN_FILENO) {
			// int dup2(int oldfd, int newfd);
			// dup2() makes newfd be the copy of oldfd, closing newfd first if necessary
			// Assim, e pelo enunciado sabemos que o programa lê a partir de stdin
			// vamos usar o dup2, para que o inout que era suposto ser lido do stdin, seja antes
			// lido do nosso pipe, no fd de leitura.
			// Basicamente, o dup2 é um canalizador
			dup2(fdsend[READ], STDIN_FILENO);
		}
		if (fdreceive[WRITE] != STDOUT_FILENO) {
			// tal como no caso anterior, vamos dizer ao gajo que o output que vá para stdout, seja antes enviado para o
			// nosso pipe, para o fd de escrita.
			dup2(fdreceive[WRITE], STDOUT_FILENO);
		}
		// E é desta forma, que conseguimos por o pai e o filho a comunicar, utilizando o canal de comunicação criado pelo pipe.

		// Agora, é chegada a hora de invocar os programas para tratar os nosso dados.
		// Recapitulando o que fizemos antes:
		// o programa espera ler do stdin, mas graças ao dup2 vai acabar por ler do nosso pipe,
		//lendo o que o pai lhe escrever
		// O programa escreve para stdout, mas mais uma vez graças ao dup2, vai acabar por mandar
		//o output para o nosso pipe (que o pai vai ler através do pipe)
		// The  exec()  functions  only return if an error has have occurred.  The
		//  return value is -1, and errno is set to indicate the error.
		execl(progname, progname, filename, NULL);
		fail("execl");
		break;

	default: // parent
		// Também o pai vai fechar as "pontas" que não lhe importa utilizar.
		// Reparar que fecha exactamente as que o filho deixou abertas
		close(fdreceive[WRITE]);
		close(fdsend[READ]);

		// Enviar o texto para o fd de escrita
		sendbuffer(fdsend[WRITE], mail, strlen(mail));
		// fechar o fd de escrita
		close(fdsend[WRITE]);

		//ler o texto filtrado, a partir do fd de leitura
		fillbuffer(fdreceive[READ], mail, BUFSIZE);
		// fechar o fd de leitura
		close(fdreceive[READ]);

		// As 3 linhas seguintes comentei-as. Não sei que estavam aqui a fazer..
		wait(NULL);
		if (strlen(mail) == 0)
			exit(EXIT_SUCCESS);
		break;
	}

	// Por fim, fechar todos os file descriptors,
	// que foram utilizados até agora, mas já não são precisos,
	// e devem ser fechados antes da app terminar.
	close(fdreceive[READ]);
	close(fdsend[WRITE]);
	return;
}

void fail(char *msg) {

	perror(msg);
	exit(EXIT_FAILURE);
}

